<?php
interface remote_export {
	public function __sleep();
	public function __wakeup();
	public function oid();
	public function __last_modified();
}
interface remote_call {
}
interface remote_placeholder {
	public function __set ( $name, $value );
	public function __get ( $name );
	public function __isset ( $name );
	public function __unset ( $name );
	public function __call ( $name, array $arguments );
}
class remote_paceholder_connection 
{
	protected $_c = null;
	protected function __construct()
	{
		//todo
	}
	public static function get_server()
	{
		//todo
		return new self();
	}
}
class remote_paceholder_universal implements remote_placeholder
{
	const
		CMD_NOOP = 0,
		CMD_REGISTER = 1,
		CMD_UNREGISTER = 2,
		CMD_SET = 3,
		CMD_GET = 4,
		CMD_CALL = 5,
		CMD_ISSET = 6,
		CMD_UNSET = 7;
	protected $_uri, $_c = null, $_my_object = null, $_client = false,
				$_my_clients = array();
	protected function __construct($uri)
	{
		$uri = parse_url($uri);
		foreach (array('scheme','host','port','user','pass') as $key) {
			if (!array_key_exists($key, $uri))
				throw new exception_remoting_baduri("bad uri $uri provided");
		}
		$this->_uri =& $uri;
		if (!in_array($uri['scheme'],array('tcp'))) //todo: support ,'udp'
			throw new exception_remoting_unsuportedschema("unsupported schema");
	}
	public function __destruct()
	{
		if ($this->_client)
			$new->__send(self::CMD_UNREGISTER);
	}
	protected static function __get_new_instance($uri) {
		//todo: override this with extending classes
		return new remote_paceholder_universal($uri);
	}
	public static function __register_client($uri)
	{
		$new = self::__get_new_instance($uri);
		$new->_client = true;
		$errno = 0;
		$errstr = '';
		$new->_c = fsockopen($uri['host'], $uri['port'], $errno, $errstr, 2.5);
		if ($errno)
			throw new exception_remoting_tcperror($errstr, $errno);
		$new->__send(self::CMD_REGISTER,array($uri['user'],$uri['pass']));
		return $new;
	}
	public static function __register_server($uri, &$obj)
	{
		$new = self::__get_new_instance($uri);
		$new->_client = true;
		$new->_my_object =& $obj;
		$errno = 0;
		$errstr = '';
		$new->_c = socket_create_listen($uri['port'], 10);
		if (socket_last_error())
			throw new exception_remoting_tcperror(socket_strerror(), socket_last_error());
		socket_set_nonblock($new->_c);
		return $new;
	}
	public function __maintain() {
		//this is the cron function that needs to be run a lot
		if ($this->_client)
		{
			while ($line = fgets($this->_c) and is_string($line))
			{
				$this->__receive($line);
			}
		} else {
			$c = socket_accept($this->_c);
			if ($c !== false) 
			{
				$this->_my_clients[] =& $c;
			}
			$my_clients = $this->_my_clients;
			$my_clients_nowdead = $my_clients;
			$null = null;
			if (0 == socket_select($my_clients, $null, $my_clients_nowdead, 0))
			{
				return;
			}
			foreach ($my_clients as &$client) 
			{
				stream_set_blocking($client, 0);
				while ($line = fgets($client) and is_string($line))
				{
					$this->__receive($line);
				}
			}
			foreach ($my_clients_dead as &$client)
			{
				socket_close($client);
				unset($this->_my_clients[array_search($client,$this->_my_clients)]);
			}
		}
	}
	protected function __send($command, $context = null, $instant = false)
	{
		if ($instant)
		{ //let's get everything before we go to instant blocking mode
			while ($line = fgets($this->_c) and is_string($line))
			{
				$this->__receive($line);
			}
		}
		if (!is_int($command) or $command < 0 or $command > 64)
			throw new excepion_remoting_badcommand("invalid command");
		fputs($this->_c,"$command ".serialize($context)."\n");
		if (!$instant) return;
		stream_set_blocking($this->_c, 1);
		stream_set_timeout($fp, 2);
		$line = fgets($this->_c);
		stream_set_blocking($this->_c, 0);
		if ($line)
		{
			return $this->__receive($line);
		}
	}
	public function __receive($line) {
		if (false === strpos(' ',$line))
			throw new exception_remoting_receivedbadline("received invalid parameters");
		list($command, $context) = explode(' ',$line,2);
		$command = (int)$command;
		$context = unserialize($context);
		switch ($command)
		{
			case self::CMD_REGISTER:
				break;
			case self::CMD_GET:
				if ($this->client)
				{
					list($name, $value) = $context;
					//todo: check names match
				} else
				{
					list($name) = $context;
					if (!isset($this->_my_object->$name))
					{
						$this->__send(CMD_GET, array($name, null));
					} else
					{
						$this->__send(CMD_GET, array($name, $this->_my_object->$name));
					}
					//todo: check that all objects are remote_export
				}
				break;
			case self::CMD_SET:
				if (!$this->client)
				{
					list($name, $value) = $context;
					$this->_my_object->$name = $value;
				}
				break;
			case self::CMD_ISSET:
				if ($this->client)
				{
					list($name, $value) = $context;
					//value is bool
				} else
				{
					list($name) = $context;
					$this->__send(CMD_GET, array($name, isset($this->_my_object->$name)));
				}
				break;
			case self::CMD_UNSET:
				if (!$this->client)
				{
					list($name) = $context;
					unset($this->_my_object->$name);
				}
				break;
			case self::CMD_CALL:
				if ($this->client)
				{
					list($name, $value) = $context;
					//value is returned from function
				} else
				{
					list($name, $parameters) = $context;
					$value = call_user_func_array(array(&$this->_my_object, $name), $parameters);
					//todo: wrap objects
					$this->__send(CMD_GET, array($name, $value));
				}
				break;
		}
		if (isset($value))
		{
			return $value;
		} else {
			return null;
		}
	}
	public function __set ( $name, $value )
	{
		$this->__send(self::CMD_SET, array($name, $value));
	}
	public function __get ( $name )
	{
		return $this->__send(self::CMD_GET, array($name), true);
	}
	public function __isset ( $name )
	{
		return $this->__send(self::CMD_ISSET, array($name), true);
	}
	public function __unset ( $name )
	{
		$this->__send(self::CMD_UNSET, array($name));
	}
	public function __call ( $name, array $arguments )
	{
		return $this->__send(self::CMD_CALL, array($name, $arguments), true);
	}
}
class remote_placeholder_connection
{
	protected function __construct()
	{
		//
	}
	public static function builder()
	{
		new self();
	}
}
class basic_placeholder implements remote_placeholder {
	//similar to the universal, only does caching
	protected $oid, $my_object = null;
	public function &__expand($err_on_fail = false) {
		//todo: fetch remote object
		$basic = $this->oid;
		protocol_module::make_basic($basic);
		if ($err_on_fail and is_null($basic))
			throw new Exception("couldn't expand placeholder {$this->oid}");
		return $basic;
	}
	public function __construct($oid) {
		$this->oid = $oid;
	}
	protected function __self_expand() {
		//todo: build chache and expiry code here
		if (!is_null($this->my_object)) return true;
		$this->my_object = $this->expand(true);
	}
	public function __set ( $name, $value ) {
		$this->__self_expand();
		$this->my_object->{$name} = $value;
	}
	public function __get ( $name ) {
		$this->__self_expand();
		return $this->my_object->{$name};
	}
	public function __isset ( $name ) {
		$this->__self_expand();
		return isset($this->my_object->{$name});
	}
	public function __unset ( $name ) {
		$this->__self_expand();
		unset($this->my_object->{$name});
	}
	public function __call ( $name, array $arguments ) {
		$this->__self_expand();
		if (!is_callable(array(&$this->my_object,$name)))
			throw new exception_remoting_nofunction("remote function not found");
		return call_user_func_array(array(&$this->my_object,$name),$arguments);
	}
	public function basic_type() {
		$this->self_expand();
		return gettype($this->my_object);
	}
}
class remoting_manager {
	protected $callbacks = array();
	public function callback_subscribe($name, array $run, array $parameters = array(),
	$priority = callback_module::PRIORITY_NORMAL)
	{
		if (!isset($this->callbacks[$name])) 
		{
			$this->callbacks[$name] = array();
		}
		$key = count($this->callbacks[$name]);
		if (!preg_match('/^[a-z_]$/',$name))
			throw new exception_remoting_badcallbackname("invalid callback name");
		eval('$this->callbacks[$name][$key] = new callback_'.$name.'($run, $parameters, $priority);');
		return $key;
	}
	public function callback_unsubscribe() 
	{
	}
}
?>
